#pragma once

#include "../widgets.h"
#include "basechart_datacontainer.h"
#include "basechart_plottable.h"
#include "basechart_style.h"

class BaseChartAxis;
class S_WIDGETS_EXPORT BaseChartGraphData {
 public:
  BaseChartGraphData();
  BaseChartGraphData(double key, double value);

  inline double sortKey() const { return key; }
  inline static BaseChartGraphData fromSortKey(double sortKey) {
    return BaseChartGraphData(sortKey, 0);
  }
  inline static bool sortKeyIsMainKey() { return true; }

  inline double mainKey() const { return key; }
  inline double mainValue() const { return value; }

  inline BaseChartRange valueRange() const {
    return BaseChartRange(value, value);
  }

  double key, value;
};

/*! \typedef QCPGraphDataContainer

  Container for storing \ref BaseChartGraphData points. The data is stored
  sorted by \a key.

  This template instantiation is the container in which BaseChartGraph holds its
  data. For details about the generic container, see the documentation of the
  class template \ref BaseChartDataContainer.

  \see BaseChartGraphData, BaseChartGraph::setData
*/
typedef BaseChartDataContainer<BaseChartGraphData> QCPGraphDataContainer;

class S_WIDGETS_EXPORT BaseChartGraph
    : public BaseChartAbstractPlottable1D<BaseChartGraphData> {
  Q_OBJECT
  /// \cond INCLUDE_QPROPERTIES
  Q_PROPERTY(LineStyle lineStyle READ lineStyle WRITE setLineStyle)
  Q_PROPERTY(BaseChartScatterStyle scatterStyle READ scatterStyle WRITE
                 setScatterStyle)
  Q_PROPERTY(int scatterSkip READ scatterSkip WRITE setScatterSkip)
  Q_PROPERTY(BaseChartGraph* channelFillGraph READ channelFillGraph WRITE
                 setChannelFillGraph)
  Q_PROPERTY(
      bool adaptiveSampling READ adaptiveSampling WRITE setAdaptiveSampling)
  /// \endcond
 public:
  /*!
    Defines how the graph's line is represented visually in the plot. The line
    is drawn with the current pen of the graph (\ref setPen). \see setLineStyle
  */
  enum LineStyle {
    lsNone  ///< data points are not connected with any lines (e.g. data only
            ///< represented with symbols according to the scatter style, see
            ///< \ref setScatterStyle)
    ,
    lsLine  ///< data points are connected by a straight line
    ,
    lsStepLeft  ///< line is drawn as steps where the step height is the value
                ///< of the left data point
    ,
    lsStepRight  ///< line is drawn as steps where the step height is the value
                 ///< of the right data point
    ,
    lsStepCenter  ///< line is drawn as steps where the step is in between two
                  ///< data points
    ,
    lsImpulse  ///< each data point is represented by a line parallel to the
               ///< value axis, which reaches from the data point to the
               ///< zero-value-line
  };
  Q_ENUMS(LineStyle)

  explicit BaseChartGraph(BaseChartAxis* keyAxis, BaseChartAxis* valueAxis);
  virtual ~BaseChartGraph() Q_DECL_OVERRIDE;

  // getters:
  QSharedPointer<QCPGraphDataContainer> data() const { return mDataContainer; }
  LineStyle lineStyle() const { return mLineStyle; }
  BaseChartScatterStyle scatterStyle() const { return mScatterStyle; }
  int scatterSkip() const { return mScatterSkip; }
  BaseChartGraph* channelFillGraph() const { return mChannelFillGraph.data(); }
  bool adaptiveSampling() const { return mAdaptiveSampling; }

  // setters:
  void setData(QSharedPointer<QCPGraphDataContainer> data);
  void setData(const QVector<double>& keys, const QVector<double>& values,
               bool alreadySorted = false);
  void setLineStyle(LineStyle ls);
  void setScatterStyle(const BaseChartScatterStyle& style);
  void setScatterSkip(int skip);
  void setChannelFillGraph(BaseChartGraph* targetGraph);
  void setAdaptiveSampling(bool enabled);

  // non-property methods:
  void addData(const QVector<double>& keys, const QVector<double>& values,
               bool alreadySorted = false);
  void addData(double key, double value);

  // reimplemented virtual methods:
  virtual double selectTest(const QPointF& pos, bool onlySelectable,
                            QVariant* details = nullptr) const Q_DECL_OVERRIDE;
  virtual BaseChartRange getKeyRange(
      bool& foundRange, SignDomain inSignDomain = sdBoth) const Q_DECL_OVERRIDE;
  virtual BaseChartRange getValueRange(
      bool& foundRange, SignDomain inSignDomain = sdBoth,
      const BaseChartRange& inKeyRange = BaseChartRange()) const
      Q_DECL_OVERRIDE;

 protected:
  // property members:
  LineStyle mLineStyle;
  BaseChartScatterStyle mScatterStyle;
  int mScatterSkip;
  QPointer<BaseChartGraph> mChannelFillGraph;
  bool mAdaptiveSampling;

  // reimplemented virtual methods:
  virtual void draw(BaseChartPainter* painter) Q_DECL_OVERRIDE;
  virtual void drawLegendIcon(BaseChartPainter* painter,
                              const QRectF& rect) const Q_DECL_OVERRIDE;

  // introduced virtual methods:
  virtual void drawFill(BaseChartPainter* painter,
                        QVector<QPointF>* lines) const;
  virtual void drawScatterPlot(BaseChartPainter* painter,
                               const QVector<QPointF>& scatters,
                               const BaseChartScatterStyle& style) const;
  virtual void drawLinePlot(BaseChartPainter* painter,
                            const QVector<QPointF>& lines) const;
  virtual void drawImpulsePlot(BaseChartPainter* painter,
                               const QVector<QPointF>& lines) const;

  virtual void getOptimizedLineData(
      QVector<BaseChartGraphData>* lineData,
      const QCPGraphDataContainer::const_iterator& begin,
      const QCPGraphDataContainer::const_iterator& end) const;
  virtual void getOptimizedScatterData(
      QVector<BaseChartGraphData>* scatterData,
      QCPGraphDataContainer::const_iterator begin,
      QCPGraphDataContainer::const_iterator end) const;

  // non-virtual methods:
  void getVisibleDataBounds(QCPGraphDataContainer::const_iterator& begin,
                            QCPGraphDataContainer::const_iterator& end,
                            const BaseChartDataRange& rangeRestriction) const;
  void getLines(QVector<QPointF>* lines,
                const BaseChartDataRange& dataRange) const;
  void getScatters(QVector<QPointF>* scatters,
                   const BaseChartDataRange& dataRange) const;
  QVector<QPointF> dataToLines(const QVector<BaseChartGraphData>& data) const;
  QVector<QPointF> dataToStepLeftLines(
      const QVector<BaseChartGraphData>& data) const;
  QVector<QPointF> dataToStepRightLines(
      const QVector<BaseChartGraphData>& data) const;
  QVector<QPointF> dataToStepCenterLines(
      const QVector<BaseChartGraphData>& data) const;
  QVector<QPointF> dataToImpulseLines(
      const QVector<BaseChartGraphData>& data) const;
  QVector<BaseChartDataRange> getNonNanSegments(
      const QVector<QPointF>* lineData, Qt::Orientation keyOrientation) const;
  QVector<QPair<BaseChartDataRange, BaseChartDataRange> >
  getOverlappingSegments(QVector<BaseChartDataRange> thisSegments,
                         const QVector<QPointF>* thisData,
                         QVector<BaseChartDataRange> otherSegments,
                         const QVector<QPointF>* otherData) const;
  bool segmentsIntersect(double aLower, double aUpper, double bLower,
                         double bUpper, int& bPrecedence) const;
  QPointF getFillBasePoint(QPointF matchingDataPoint) const;
  const QPolygonF getFillPolygon(const QVector<QPointF>* lineData,
                                 BaseChartDataRange segment) const;
  const QPolygonF getChannelFillPolygon(const QVector<QPointF>* thisData,
                                        BaseChartDataRange thisSegment,
                                        const QVector<QPointF>* otherData,
                                        BaseChartDataRange otherSegment) const;
  int findIndexBelowX(const QVector<QPointF>* data, double x) const;
  int findIndexAboveX(const QVector<QPointF>* data, double x) const;
  int findIndexBelowY(const QVector<QPointF>* data, double y) const;
  int findIndexAboveY(const QVector<QPointF>* data, double y) const;
  double pointDistance(
      const QPointF& pixelPoint,
      QCPGraphDataContainer::const_iterator& closestData) const;

  friend class BaseChartCustomPlot;
  friend class QCPLegend;
};

Q_DECLARE_TYPEINFO(BaseChartGraphData, Q_PRIMITIVE_TYPE);
Q_DECLARE_METATYPE(BaseChartGraph::LineStyle)
